/*
 *   
 *
 * Copyright  1990-2009 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */
package com.sun.midp.jsr82emul;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.SocketConnection;
import com.sun.jsr082.bluetooth.BluetoothUrl;
import java.util.Hashtable;
import java.util.Enumeration;

/*
 * Handles communication with a client.
 */
class ClientHandler implements Runnable {
    /* Handle that identifies corresponding data on server. */
    int handle;
    /* Common handlers counter. */
    private static int nextHandle = 0;
    
    /* Bluetooth address of device or device emulation client belongs to. */
    byte[] bluetoothAddress = null;
    
    /* If not <code>null</code>, IP address of computer client works at. */
    byte[] ipAddress = null;
    
    /*
     * Keeps device state if this handler handles device, 
     * <code>null</code> otherwise. 
     */
    DeviceState deviceState = null;
        
    /*
     * if this handler handles device, then keeps services available at it,
     * else <code>null</code>. 
     */
    Hashtable deviceServices = null;
        
    /*
     * Keeps messenger that provides utilities for communicating with client.
     */
    private Messenger messenger = new Messenger();
    
    /* Keeps socket connection to the server. */
    private SocketConnection connection; 
    /* Keeps input stream from server. */
    private InputStream fromClient;                                   
    /* Keeps output stream to server. */
    private OutputStream toClient;
    
    private boolean inProcess = false;
    
    /* 
     * Retrieves handle value for a new handler. 
     * @return new free handle value
     */
    private static synchronized int getNextHandle() {
        return ++nextHandle;
    }
    
    /*
     * Constructs client handle to communicate with a client thru
     * given connection.
     * @param connection an open connection with a client on the other side
     * @exception IOException if connection fails
     */
    ClientHandler(SocketConnection connection) throws Exception {
        this.connection = connection;
        fromClient = connection.openDataInputStream();
        toClient = connection.openDataOutputStream();
        handle = getNextHandle();
        
        Thread t = new Thread(this);
        t.start();
    }

    /* 
     * Implements <code>Runnable</code>, processes client's rquests.
     */
    public void run() {
        Exception exception = null;
    
        try {
            loop:
            while (true) {
                messenger.receive(fromClient);
            
                switch (messenger.getCode()) {
                    case Messenger.REGISTER_DEVICE:
                        synchronized (messenger) {
                            inProcess = true;
                            try {
                                ipAddress = messenger.getBytes();
                                deviceServices = new Hashtable();
                                // IMPL_NOTE: how it works against Push TCK tests?
                                // default device state: not discoverable
                                deviceState = new DeviceState();
                                bluetoothAddress = EmulationServer
                                        .getInstance().registerDevice(
                                                deviceState);
                                messenger.sendBytes(toClient,
                                        Messenger.REGISTERED, bluetoothAddress);
                            } finally {
                                inProcess = false;
                                messenger.notify();
                            }
                        }
                        break;
                        
                    case Messenger.UPDATE_DEVICE_STATE:
                        synchronized (messenger) {
                            deviceState.update(messenger.getInt());
                        }
                        break;
                        
                    case Messenger.REGISTER_SERVICE:
                        ServiceConnectionData serv;
                        ServiceKey key;
        
                        synchronized (messenger) {
                            inProcess = true;
                            try {
                                serv = new ServiceConnectionData(messenger
                                        .getBytes(),
                                        ServiceConnectionData.SERVER_DATA);
                                serv.address = ipAddress;
                                key = EmulationServer.getInstance()
                                        .registerService(serv, bluetoothAddress);
                                if (!deviceServices.containsKey(key)) {
                                    deviceServices.put(
                                            new Integer(serv.socketPort), key);
                                }
                            } finally {
                                inProcess = false;
                                messenger.notify();
                            }
                        }
                        break;

                    case Messenger.UNREGISTER_SERVICE:
                        synchronized (messenger) {
                            key = (ServiceKey) deviceServices
                                    .remove(new Integer(messenger.getInt()));
                            if (key != null) {
                                EmulationServer.getInstance()
                                        .unregisterService(key);
                                key = null;
                            }
                        }
                        break;
                                                
                    case Messenger.CONNECT_TO_SERVICE:
                        synchronized (messenger) {
                            int attempts = 0;
                            while (inProcess && attempts < 100) {
                                try {
                                    messenger.wait(200);
                                    attempts += 1;
                                } catch (InterruptedException e) {
                                    throw new EmulationException("Service isn't registred");
                                }
                            }
                            serv = EmulationServer
                                .getInstance()
                                .connectToService(
                                    new ServiceConnectionData(
                                            messenger.getBytes(),
                                            ServiceConnectionData.CONN_REQUEST_DATA));
                            messenger
                                .sendBytes(
                                        toClient,
                                        Messenger.SERVICE_AT,
                                        serv
                                            .toByteArray(ServiceConnectionData.CONNECTION_DATA));
                        }
                        break;
                        
                    case Messenger.START_INQUIRY:
                        synchronized (messenger) {
                            InquiryResults results = EmulationServer
                                    .getInstance().runInquiry(
                                            messenger.getInt(),
                                            bluetoothAddress);
                            messenger.sendBytes(toClient,
                                    Messenger.INQUIRY_COMPLETED, results
                                            .toByteArray());
                        }
                        break;
                        
                    case Messenger.DONE:
                        break loop;
                
                    default: throw new EmulationException("Unknown client request");
                }
            } // end of loop:
        
        } catch (EmulationException ee) {
            exception = ee;
        } catch (IOException e) {
            exception = e;
        } finally {
            if (exception != null) {
                try {
                    messenger.send(toClient, Messenger.ERROR, exception.getMessage());
                } catch (IOException e) {
                    // ignoring - stop handling anyway
                }
            }
            
            if (deviceState != null) {
                EmulationServer.getInstance().
                    unregisterDevice(bluetoothAddress);
            
                Enumeration records = deviceServices.elements();
                while (records.hasMoreElements()) {
                    EmulationServer.getInstance().unregisterService(
                        (ServiceKey)records.nextElement());
                }
            }
            disconnect();
        }
    }
    
    /*
     * Closes current socket connection.
     */
    private void disconnect() {
        try { 
            connection.close();
            toClient.close();
            fromClient.close();
        } catch (IOException e) {
            // ignoring
        }
        
        connection = null;
        fromClient = null;
        toClient = null;
    }
}
